/*-------------------------------------------------------------------------
 *
 * postinit.c
 *	  PostgreSQL初始化工具
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/init/postinit.c
 *
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>

#include "access/genam.h"
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/session.h"
#include "access/sysattr.h"
#include "access/tableam.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "access/xloginsert.h"
#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_database.h"
#include "catalog/pg_db_role_setting.h"
#include "catalog/pg_tablespace.h"
#include "libpq/auth.h"
#include "libpq/libpq-be.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/postmaster.h"
#include "replication/slot.h"
#include "replication/walsender.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "storage/sync.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/pg_locale.h"
#include "utils/portal.h"
#include "utils/ps_status.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/timeout.h"

static HeapTuple fc_GetDatabaseTuple(const char *fc_dbname);
static HeapTuple fc_GetDatabaseTupleByOid(Oid fc_dboid);
static void fc_PerformAuthentication(Port *fc_port);
static void fc_CheckMyDatabase(const char *fc_name, bool fc_am_superuser, bool fc_override_allow_connections);
static void fc_ShutdownPostgres(int fc_code, Datum fc_arg);
static void fc_StatementTimeoutHandler(void);
static void fc_LockTimeoutHandler(void);
static void fc_IdleInTransactionSessionTimeoutHandler(void);
static void fc_IdleSessionTimeoutHandler(void);
static void fc_IdleStatsUpdateTimeoutHandler(void);
static void fc_ClientCheckTimeoutHandler(void);
static bool fc_ThereIsAtLeastOneRole(void);
static void fc_process_startup_options(Port *fc_port, bool fc_am_superuser);
static void fc_process_settings(Oid fc_databaseid, Oid fc_roleid);


/*** InitPostgres support ***/


/*
 * GetDatabaseTuple -- 获取数据库的 pg_database 行
 *
 * 这在后端启动期间使用，当我们还没有访问系统目录的权限时。在最坏的情况下，我们可以仅使用 relcache.c 为 pg_database 创建的硬编码描述符对 pg_database 进行顺序扫描。在更典型的情况下，relcache.c 能够从共享 relcache 缓存文件中加载 pg_database 及其索引的描述符，因此我们可以进行索引扫描。criticalSharedRelcachesBuilt 指示我们是否获得了缓存的描述符。
 */
static HeapTuple fc_GetDatabaseTuple(const char *fc_dbname)
{
	HeapTuple	fc_tuple;
	Relation	fc_relation;
	SysScanDesc fc_scan;
	ScanKeyData fc_key[1];

	/*
	 * 形成扫描键
	 */
	ScanKeyInit(&fc_key[0],
				Anum_pg_database_datname,
				BTEqualStrategyNumber, F_NAMEEQ,
				CStringGetDatum(fc_dbname));

	/*
	 * 打开 pg_database 并获取一个元组。如果我们还没有构建关键的共享 relcache 条目（即，我们在没有共享 relcache 缓存文件的情况下启动），则强制堆扫描。
	 */
	fc_relation = table_open(DatabaseRelationId, AccessShareLock);
	fc_scan = systable_beginscan(fc_relation, DatabaseNameIndexId,
							  criticalSharedRelcachesBuilt,
							  NULL,
							  1, fc_key);

	fc_tuple = systable_getnext(fc_scan);

	/* 必须在释放缓冲区之前复制元组 */
	if (HeapTupleIsValid(fc_tuple))
		fc_tuple = heap_copytuple(fc_tuple);

	/* 全部完成 */
	systable_endscan(fc_scan);
	table_close(fc_relation, AccessShareLock);

	return fc_tuple;
}

/*
 * GetDatabaseTupleByOid -- 如上所述，但按数据库 OID 搜索
 */
static HeapTuple fc_GetDatabaseTupleByOid(Oid fc_dboid)
{
	HeapTuple	fc_tuple;
	Relation	fc_relation;
	SysScanDesc fc_scan;
	ScanKeyData fc_key[1];

	/*
	 * 形成扫描键
	 */
	ScanKeyInit(&fc_key[0],
				Anum_pg_database_oid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_dboid));

	/*
	 * 打开 pg_database 并获取一个元组。如果我们还没有构建关键的共享 relcache 条目（即，我们在没有共享 relcache 缓存文件的情况下启动），则强制堆扫描。
	 */
	fc_relation = table_open(DatabaseRelationId, AccessShareLock);
	fc_scan = systable_beginscan(fc_relation, DatabaseOidIndexId,
							  criticalSharedRelcachesBuilt,
							  NULL,
							  1, fc_key);

	fc_tuple = systable_getnext(fc_scan);

	/* 必须在释放缓冲区之前复制元组 */
	if (HeapTupleIsValid(fc_tuple))
		fc_tuple = heap_copytuple(fc_tuple);

	/* 全部完成 */
	systable_endscan(fc_scan);
	table_close(fc_relation, AccessShareLock);

	return fc_tuple;
}


/*
 * PerformAuthentication -- 认证远程客户端
 *
 * 返回：无。如果发生任何失败将不会返回。
 */
static void fc_PerformAuthentication(Port *fc_port)
{
	/* 这应该已经设置，但让我们确保 */
	ClientAuthInProgress = true;	/* 限制日志消息的可见性 */

	/*
	 * 在 EXEC_BACKEND 情况下，我们没有从后端进程继承 pg_hba.conf 等的内容，必须自己加载它们。
	 *
	 * FIXME: [fork/exec] 哎。有办法避免这个开销吗？
	 */
#ifdef EXEC_BACKEND

	/*
	 * load_hba() 和 load_ident() 想要在 PostmasterContext 内工作，
	 * 所以如果它不存在（它不会），就创建它。我们稍后会在 PostgresMain 中再次删除它。
	 */
	if (PostmasterContext == NULL)
		PostmasterContext = AllocSetContextCreate(TopMemoryContext,
												  "Postmaster",
												  ALLOCSET_DEFAULT_SIZES);

	if (!load_hba())
	{
		/*
		 * 如果我们无法加载 HBA 文件，继续下去是没意义的，
		 * 因为在这种情况下无法连接到数据库。
		 */
		ereport(FATAL,
				(errmsg("could not load pg_hba.conf")));
	}

	if (!load_ident())
	{
		/*
		 * 如果我们无法加载 IDENT 文件，继续是可以的，尽管这
		 * 意味着你无法使用任何需要用户名称映射的认证方法登录。load_ident() 已经将错误的详细信息记录到日志中。
		 */
	}
#endif

	/*
	 * 设置一个超时，以防一个有缺陷或恶意的客户端在认证期间未能响应。
	 * 由于我们处于一个事务中并可能进行数据库访问，我们必须使用 statement_timeout 基础设施。
	 */
	enable_timeout_after(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000);

	/*
	 * 现在执行认证交换。
	 */
	set_ps_display("authentication");
	ClientAuthentication(fc_port); /* 可能不会返回，如果发生失败 */

	/*
	 * 认证完成。禁用超时，并在需要时记录。
	 */
	disable_timeout(STATEMENT_TIMEOUT, false);

	if (Log_connections)
	{
		StringInfoData fc_logmsg;

		initStringInfo(&fc_logmsg);
		if (am_walsender)
			appendStringInfo(&fc_logmsg, _("replication connection authorized: user=%s"),
							 fc_port->user_name);
		else
			appendStringInfo(&fc_logmsg, _("connection authorized: user=%s"),
							 fc_port->user_name);
		if (!am_walsender)
			appendStringInfo(&fc_logmsg, _(" database=%s"), fc_port->database_name);

		if (fc_port->application_name != NULL)
			appendStringInfo(&fc_logmsg, _(" application_name=%s"),
							 fc_port->application_name);

#ifdef USE_SSL
		if (fc_port->ssl_in_use)
			appendStringInfo(&fc_logmsg, _(" SSL enabled (protocol=%s, cipher=%s, bits=%d)"),
							 be_tls_get_version(fc_port),
							 be_tls_get_cipher(fc_port),
							 be_tls_get_cipher_bits(fc_port));
#endif
#ifdef ENABLE_GSS
		if (fc_port->gss)
		{
			const char *fc_princ = be_gssapi_get_princ(fc_port);

			if (fc_princ)
				appendStringInfo(&fc_logmsg,
								 _(" GSS (authenticated=%s, encrypted=%s, principal=%s)"),
								 be_gssapi_get_auth(fc_port) ? _("yes") : _("no"),
								 be_gssapi_get_enc(fc_port) ? _("yes") : _("no"),
								 fc_princ);
			else
				appendStringInfo(&fc_logmsg,
								 _(" GSS (authenticated=%s, encrypted=%s)"),
								 be_gssapi_get_auth(fc_port) ? _("yes") : _("no"),
								 be_gssapi_get_enc(fc_port) ? _("yes") : _("no"));
		}
#endif

		ereport(LOG, errmsg_internal("%s", fc_logmsg.data));
		pfree(fc_logmsg.data);
	}

	set_ps_display("startup");

	ClientAuthInProgress = false;	/* client_min_messages 现在处于活动状态 */
}


/*
 * CheckMyDatabase -- 获取我们数据库的 pg_database 条目的信息
 */
static void fc_CheckMyDatabase(const char *fc_name, bool fc_am_superuser, bool fc_override_allow_connections)
{
	HeapTuple	fc_tup;
	Form_pg_database fc_dbform;
	Datum		fc_datum;
	bool		fc_isnull;
	char	   *fc_collate;
	char	   *fc_ctype;
	char	   *fc_iculocale;

	/* 正常通过 syscache 获取我们的 pg_database 行 */
	fc_tup = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
	fc_dbform = (Form_pg_database) GETSTRUCT(fc_tup);

	/* 这个复查纯粹是出于多疑 */
	if (strcmp(fc_name, NameStr(fc_dbform->datname)) != 0)
		ereport(FATAL,
				(errcode(ERRCODE_UNDEFINED_DATABASE),
				 errmsg("database \"%s\" has disappeared from pg_database",
						fc_name),
				 errdetail("Database OID %u now seems to belong to \"%s\".",
						   MyDatabaseId, NameStr(fc_dbform->datname))));

	/*
	 * 检查连接到数据库的权限。
	 *
	 * 在独立模式下，这些检查不被强制执行，以便于从禁用对所有数据库的所有访问中恢复，例如 "UPDATE pg_database SET datallowconn = false;"。
	 */
	if (IsUnderPostmaster)
	{
		/*
		 * 检查数据库当前是否允许连接。
		 * (后台进程可以通过设置 override_allow_connections 来覆盖此测试和下一个测试。)
		 */
		if (!fc_dbform->datallowconn && !fc_override_allow_connections)
			ereport(FATAL,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("database \"%s\" is not currently accepting connections",
							fc_name)));

		/*
		 * 检查连接到数据库的特权。  (am_superuser 测试是冗余的，但是既然我们有这个标志，就检查它并节省一些周期。)
		 */
		if (!fc_am_superuser && !fc_override_allow_connections &&
			pg_database_aclcheck(MyDatabaseId, GetUserId(),
								 ACL_CONNECT) != ACLCHECK_OK)
			ereport(FATAL,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("permission denied for database \"%s\"", fc_name),
					 errdetail("User does not have CONNECT privilege.")));

		/*
		 * 检查此数据库的连接限制。  我们仅对常规后端强制执行该限制，因为其他进程类型有自己的 PGPROC 池。
		 *
		 * 这里存在竞争条件 --- 我们在检查其他 PGPROCs 之前创建我们的 PGPROC。  如果两个后端几乎同时执行此操作，它们可能都会认为自己超出了限制，而理想情况下，一个应该成功，一个应该失败。  使其完全正常工作似乎比值得付出更多的麻烦；相反，我们只是记录连接限制是近似的。
		 */
		if (fc_dbform->datconnlimit >= 0 &&
			AmRegularBackendProcess() &&
			!fc_am_superuser &&
			CountDBConnections(MyDatabaseId) > fc_dbform->datconnlimit)
			ereport(FATAL,
					(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
					 errmsg("too many connections for database \"%s\"",
							fc_name)));
	}

	/*
	 * 好的，我们成功了。  下一步要做的是从 pg_database 元组中保存编码信息。
	 */
	SetDatabaseEncoding(fc_dbform->encoding);
	/* 将其记录为 GUC 内部选项 */
	SetConfigOption("server_encoding", GetDatabaseEncodingName(),
					PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT);
	/* 如果我们没有其他 client_encoding 的来源，使用服务器编码 */
	SetConfigOption("client_encoding", GetDatabaseEncodingName(),
					PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT);

	/* 分配区域设置变量 */
	fc_datum = SysCacheGetAttr(DATABASEOID, fc_tup, Anum_pg_database_datcollate, &fc_isnull);
	Assert(!fc_isnull);
	fc_collate = TextDatumGetCString(fc_datum);
	fc_datum = SysCacheGetAttr(DATABASEOID, fc_tup, Anum_pg_database_datctype, &fc_isnull);
	Assert(!fc_isnull);
	fc_ctype = TextDatumGetCString(fc_datum);

	if (pg_perm_setlocale(LC_COLLATE, fc_collate) == NULL)
		ereport(FATAL,
				(errmsg("database locale is incompatible with operating system"),
				 errdetail("The database was initialized with LC_COLLATE \"%s\", "
						   " which is not recognized by setlocale().", fc_collate),
				 errhint("Recreate the database with another locale or install the missing locale.")));

	if (pg_perm_setlocale(LC_CTYPE, fc_ctype) == NULL)
		ereport(FATAL,
				(errmsg("database locale is incompatible with operating system"),
				 errdetail("The database was initialized with LC_CTYPE \"%s\", "
						   " which is not recognized by setlocale().", fc_ctype),
				 errhint("Recreate the database with another locale or install the missing locale.")));

	if (strcmp(fc_ctype, "C") == 0 ||
		strcmp(fc_ctype, "POSIX") == 0)
		database_ctype_is_c = true;

	if (fc_dbform->datlocprovider == COLLPROVIDER_ICU)
	{
		fc_datum = SysCacheGetAttr(DATABASEOID, fc_tup, Anum_pg_database_daticulocale, &fc_isnull);
		Assert(!fc_isnull);
		fc_iculocale = TextDatumGetCString(fc_datum);
		make_icu_collator(fc_iculocale, &default_locale);
	}
	else
		fc_iculocale = NULL;

	default_locale.provider = fc_dbform->datlocprovider;

	/*
	 * 默认区域设置当前始终是确定性的。  非确定性
	 * 区域设置目前不支持模式匹配，如果全局应用会破坏很多东西。
	 */
	default_locale.deterministic = true;

	/*
	 * 检查排序规则版本。  参见 pg_newlocale_from_collation() 中的类似代码。  请注意，这里我们在任何情况下都警告而不是出错，以便不阻止连接。
	 */
	fc_datum = SysCacheGetAttr(DATABASEOID, fc_tup, Anum_pg_database_datcollversion,
							&fc_isnull);
	if (!fc_isnull)
	{
		char	   *fc_actual_versionstr;
		char	   *fc_collversionstr;

		fc_collversionstr = TextDatumGetCString(fc_datum);

		fc_actual_versionstr = get_collation_actual_version(fc_dbform->datlocprovider, fc_dbform->datlocprovider == COLLPROVIDER_ICU ? fc_iculocale : fc_collate);
		if (!fc_actual_versionstr)
			/* 不应该发生 */
			elog(WARNING,
				 "database \"%s\" has no actual collation version, but a version was recorded",
				 fc_name);
		else if (strcmp(fc_actual_versionstr, fc_collversionstr) != 0)
			ereport(WARNING,
					(errmsg("database \"%s\" has a collation version mismatch",
							fc_name),
					 errdetail("The database was created using collation version %s, "
							   "but the operating system provides version %s.",
							   fc_collversionstr, fc_actual_versionstr),
					 errhint("Rebuild all objects in this database that use the default collation and run "
							 "ALTER DATABASE %s REFRESH COLLATION VERSION, "
							 "or build PostgreSQL with the right library version.",
							 quote_identifier(fc_name))));
	}

	/* 使区域设置设置也作为 GUC 变量可见 */
	SetConfigOption("lc_collate", fc_collate, PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT);
	SetConfigOption("lc_ctype", fc_ctype, PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT);

	check_strxfrm_bug();

	ReleaseSysCache(fc_tup);
}


/*
 * pg_split_opts -- 拆分选项字符串并将其附加到 argv 数组
 *
 * 调用者负责确保 argv 数组足够大。  此例程添加的最大参数数量是
 * (strlen(optstr) + 1) / 2。
 *
 * 由于某些选项值可以包含空格，因此我们允许使用反斜杠进行转义，\\ 表示字面上的反斜杠。
 */
void pg_split_opts(char **fc_argv, int *fc_argcp, const char *fc_optstr)
{
	StringInfoData fc_s;

	initStringInfo(&fc_s);

	while (*fc_optstr)
	{
		bool		fc_last_was_escape = false;

		resetStringInfo(&fc_s);

		/* 跳过前导空格 */
		while (isspace((unsigned char) *fc_optstr))
			fc_optstr++;

		if (*fc_optstr == '\0')
			break;

		/*
		 * 解析单个选项，直到第一个空格，除非它被转义。
		 */
		while (*fc_optstr)
		{
			if (isspace((unsigned char) *fc_optstr) && !fc_last_was_escape)
				break;

			if (!fc_last_was_escape && *fc_optstr == '\\')
				fc_last_was_escape = true;
			else
			{
				fc_last_was_escape = false;
				appendStringInfoChar(&fc_s, *fc_optstr);
			}

			fc_optstr++;
		}

		/* 现在将选项存储在下一个 argv[] 位置 */
		fc_argv[(*fc_argcp)++] = pstrdup(fc_s.data);
	}

	pfree(fc_s.data);
}

/*
 * 从配置选项初始化 MaxBackends 值。
 *
 * 这必须在模块有机会更改共享预加载库中的 GUC 之后，以及在确定共享内存大小之前调用。
 *
 * 请注意，在 EXEC_BACKEND 环境中，该值通过 SubPostmasterMain 中的 BackendParameters 从主进程传递到子进程；只有主进程本身和不受主进程控制的进程应该调用这个。
 */
void InitializeMaxBackends(void)
{
	Assert(MaxBackends == 0);

	/* 额外的单位用于计入 autovacuum 启动器 */
	MaxBackends = MaxConnections + autovacuum_max_workers + 1 +
		max_worker_processes + max_wal_senders;

	/* 内部错误，因为所有值之前都已经检查过 */
	if (MaxBackends > MAX_BACKENDS)
		elog(ERROR, "too many backends configured");
}

/*
 * 后台的早期初始化（无论是独立的还是在主进程下）。
 * 这发生在 InitPostgres 之前。
 *
 * 这与 InitPostgres 是分开的，因为它也被辅助进程调用，
 * 比如后台写入进程，这些进程可能根本不会调用
 * InitPostgres。
 */
void BaseInit(void)
{
	Assert(MyProc != NULL);

	/*
	 * 初始化我们的输入/输出/调试文件描述符。
	 */
	DebugFileOpen();

	/*
	 * 初始化文件访问。尽早完成，以便其他子系统可以访问
	 * 文件。
	 */
	InitFileAccess();

	/*
	 * 初始化统计信息报告。需要尽早进行，以确保
	 * pgstat 的关闭回调在所有可以生成统计信息的子系统的
	 * 关闭回调之后运行（例如，事务提交可以）。
	 */
	pgstat_initialize();

	/* 执行存储和缓冲区管理器的本地初始化 */
	InitSync();
	smgrinit();
	InitBufferPoolAccess();

	/*
	 * 在 pgstat 后初始化临时文件访问，以便临时
	 * 文件关闭钩子可以报告临时文件统计信息。
	 */
	InitTemporaryFileAccess();

	/*
	 * 初始化 WAL 记录构建的本地点缓冲区，以防我们曾经
	 * 尝试插入 XLOG。
	 */
	InitXLogInsert();

	/*
	 * 在 pgstat 后初始化复制槽。退出钩子可能需要
	 * 删除临时槽，这反过来又触发统计信息报告。
	 */
	ReplicationSlotInitialize();
}


/* --------------------------------
 * InitPostgres
 *		初始化 POSTGRES。
 *
 * 参数：
 *	in_dbname, dboid：指定要连接的数据库，如下所述
 *	username, useroid：指定要作为的角色，如下所述
 *	load_session_libraries：TRUE 表示尊重 [session|local]_preload_libraries
 *	override_allow_connections：TRUE 表示尽管 !datallowconn 仍然连接
 *	out_dbname：可选输出参数，见下文；如果不使用，则传 NULL
 *
 * 数据库可以通过名称使用 in_dbname 参数指定，或通过 OID 使用 dboid 参数指定。
 * 分别为未使用的参数指定 NULL 或 InvalidOid。如果提供了 dboid，实际的数据库名称
 * 可以返回给调用者的 out_dbname。如果 out_dbname 不是 NULL，它必须指向大小为 NAMEDATALEN 的缓冲区。
 *
 * 同样，角色可以通过名称使用 username 参数传递，或通过 OID 使用 useroid 参数。
 *
 * 在引导模式下，数据库和用户名参数为 NULL/InvalidOid。
 * 自动真空启动进程也不指定这些参数，因为它只够读 pg_database；它不连接到任何特定的数据库。
 * 自动真空工作进程指定了一个数据库，但没有指定用户名；相反，物理 walsender 指定
 * 用户名但没有指定数据库。
 *
 * 按照惯例，load_session_libraries 应在“交互式”会话（包括独立后端）中传递为 true，
 * 但在自动真空等后台进程中传递为 false。特别注意，它在并行工作进程中不应为 true；
 * 这些进程有另一种机制来复制其领导者加载的库集。
 *
 * 我们期望 InitProcess() 已经被调用，因此我们已拥有一个 PGPROC 结构……但它尚未完全填充。
 *
 * 注意：
 *		在 InitPostgres 函数中的调用顺序要非常小心。
 * --------------------------------
 */
void InitPostgres(const char *fc_in_dbname, Oid fc_dboid,
			 const char *fc_username, Oid fc_useroid,
			 bool fc_load_session_libraries,
			 bool fc_override_allow_connections,
			 char *fc_out_dbname)
{
	bool		fc_bootstrap = IsBootstrapProcessingMode();
	bool		fc_am_superuser;
	char	   *fc_fullpath;
	char		fc_dbname[NAMEDATALEN];

	elog(DEBUG3, "InitPostgres");

	/*
	 * 将我的 PGPROC 结构添加到 ProcArray。
	 *
	 * 一旦我完成了这个，我对其他后端是可见的！
	 */
	InitProcessPhase2();

	/*
	 * 初始化我在共享失效管理器的每个后端数据数组中的条目。
	 *
	 * 设置 MyBackendId，一个唯一的后端标识符。
	 */
	MyBackendId = InvalidBackendId;

	SharedInvalBackendInit(false);

	if (MyBackendId > MaxBackends || MyBackendId <= 0)
		elog(FATAL, "bad backend ID: %d", MyBackendId);

	/* 现在我们有了 BackendId，可以参与 ProcSignal */
	ProcSignalInit(MyBackendId);

	/*
	 * 还需要设置后端操作所需的超时处理程序。在每种情况下我们都需要这些，除了引导。
	 */
	if (!fc_bootstrap)
	{
		RegisterTimeout(DEADLOCK_TIMEOUT, CheckDeadLockAlert);
		RegisterTimeout(STATEMENT_TIMEOUT, fc_StatementTimeoutHandler);
		RegisterTimeout(LOCK_TIMEOUT, fc_LockTimeoutHandler);
		RegisterTimeout(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
						fc_IdleInTransactionSessionTimeoutHandler);
		RegisterTimeout(IDLE_SESSION_TIMEOUT, fc_IdleSessionTimeoutHandler);
		RegisterTimeout(CLIENT_CONNECTION_CHECK_TIMEOUT, fc_ClientCheckTimeoutHandler);
		RegisterTimeout(IDLE_STATS_UPDATE_TIMEOUT,
						fc_IdleStatsUpdateTimeoutHandler);
	}

	/*
	 * 如果这是引导进程或独立后端，启动 XLOG 机制，并注册在退出时关闭它。
	 * 在其他情况下，启动进程负责启动 XLOG 机制，而检查点则负责关闭它。
	 */
	if (!IsUnderPostmaster)
	{
		/*
		 * 我们还没有辅助进程资源所有者，但 StartupXLOG
		 * 和 ShutdownXLOG 将需要一个。因此，创建该资源所有者
		 * （并注册一个回调，以在 ShutdownXLOG 运行后清理它）。
		 */
		CreateAuxProcessResourceOwner();

#ifdef FDD
		if(fc_bootstrap)
		{
		    //用于BootstrapModeMain进程检测是否开启TDE，并且将相关加密密钥赋值到ControlFile中保存  
			fdb_BootstrapModeMain_set_tde();
		}
#endif
		StartupXLOG();
		/* 释放（并警告）在 StartupXLOG 中泄漏的任何缓冲区指针 */
		ReleaseAuxProcessResources(true);
		/* 目前将 CurrentResourceOwner 重置为空 */
		CurrentResourceOwner = NULL;

		/*
		 * 使用 before_shmem_exit()，以便 ShutdownXLOG() 可以依赖
		 * DSM 段等的正常工作（这在 pgstats 中是必需的）。
		 */
		before_shmem_exit(pgstat_before_server_shutdown, 0);
		before_shmem_exit(ShutdownXLOG, 0);
	}

	/*
	 * 初始化关系缓存和系统目录缓存。请注意，这里不会发生任何目录访问；
	 * 我们只是设置哈希表结构。我们必须在开始事务之前执行此操作，
	 * 因为事务中止将尝试访问这些哈希表。
	 */
	RelationCacheInitialize();
	InitCatalogCache();
	InitPlanCache();

	/* 初始化门户管理器 */
	EnablePortalManager();

	/* 初始化状态报告 */
	pgstat_beinit();

	/*
	 * 为共享系统目录加载关系缓存条目。这必须至少创建
	 * pg_database 和用于认证的目录的条目。
	 */
	RelationCacheInitializePhase2();

	/*
	 * 设置进程退出回调以进行预关机清理。这是我们注册的
	 * 第一个 before_shmem_exit 回调之一；因此，这将是
	 * 在低级模块（如缓冲区管理器）开始关闭之前我们做的最后几件事之一。
	 * 我们需要在开始第一次事务之前做好准备---如果在
	 * 初始化事务期间失败，那么我们需要 AbortTransaction 调用来清理。
	 */
	before_shmem_exit(fc_ShutdownPostgres, 0);

	/* autovacuum 启动器在这里完成 */
	if (IsAutoVacuumLauncherProcess())
	{
		/* 在 PgBackendStatus 数组中报告此后端 */
		pgstat_bestart();

		return;
	}

	/*
	 * 在首次访问数据库之前在这里启动新事务，并获取快照。
	 * 我们本身不需要快照，但我们对它设置 RecentGlobalXmin 的
	 * 次要效果很感兴趣。（这对于任何读取堆页面的内容都是至关重要的，
	 * 因为 HOT 可能会决定修剪它们，即使该过程没有尝试修改任何元组。）
	 *
	 * FIXME：此注释不准确/代码存在 bug。未推送/未激活的快照
	 * 并不能可靠地防止 HOT 修剪（->xmin 可能在处理缓存失效时被清除）。
	 */
	if (!fc_bootstrap)
	{
		/* 必须设置 statement_timestamp 才能正确处理超时 */
		SetCurrentStatementStartTimestamp();
		StartTransactionCommand();

		/*
		 * transaction_isolation 将由上述设置为默认值。
		 * 如果默认值为“可串行化”，而我们处于热备份状态，
		 * 如果不将其更改为较低的值，我们将失败。
		 * 幸运的是，“读取已提交”就足够好。
		 */
		XactIsoLevel = XACT_READ_COMMITTED;

		(void) GetTransactionSnapshot();
	}

	/*
	 * 如有必要执行客户端认证，然后确定我们的
	 * postgres 用户 ID，并查看我们是否是超级用户。
	 *
	 * 在独立模式和 autovacuum 工作进程中，我们使用固定
	 * ID，否则我们从经过身份验证的用户名中确定它。
	 */
	if (fc_bootstrap || IsAutoVacuumWorkerProcess())
	{
		InitializeSessionUserIdStandalone();
		fc_am_superuser = true;
	}
	else if (!IsUnderPostmaster)
	{
		InitializeSessionUserIdStandalone();
		fc_am_superuser = true;
		if (!fc_ThereIsAtLeastOneRole())
			ereport(WARNING,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("no roles are defined in this database system"),
					 errhint("You should immediately run CREATE USER \"%s\" SUPERUSER;.",
							 fc_username != NULL ? fc_username : "postgres")));
	}
	else if (IsBackgroundWorker)
	{
		if (fc_username == NULL && !OidIsValid(fc_useroid))
		{
			InitializeSessionUserIdStandalone();
			fc_am_superuser = true;
		}
		else
		{
			InitializeSessionUserId(fc_username, fc_useroid);
			fc_am_superuser = superuser();
		}
	}
	else
	{
		/* 正常的多用户案例 */
		Assert(MyProcPort != NULL);
		fc_PerformAuthentication(MyProcPort);
		InitializeSessionUserId(fc_username, fc_useroid);
		fc_am_superuser = superuser();
	}

	/*
	 * 二进制升级仅允许超级用户连接
	 */
	if (IsBinaryUpgrade && !fc_am_superuser)
	{
		ereport(FATAL,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to connect in binary upgrade mode")));
	}

	/*
	 * 最后几个常规连接槽为超级用户保留。我们
	 * 不会对后台进程应用此限制，因为它们都有
	 * 自己的 PGPROC 槽池。
	 */
	if (AmRegularBackendProcess() && !fc_am_superuser &&
		ReservedBackends > 0 &&
		!HaveNFreeProcs(ReservedBackends))
		ereport(FATAL,
				(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
				 errmsg("remaining connection slots are reserved for non-replication superuser connections")));

	/* 检查 walsender 进程所需的复制权限。 */
	if (am_walsender)
	{
		Assert(!fc_bootstrap);

		if (!superuser() && !has_rolreplication(GetUserId()))
			ereport(FATAL,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("must be superuser or replication role to start walsender")));
	}

	/*
	 * 如果这是一个仅支持物理复制的普通walsender，我们
	 * 不想连接到任何特定的数据库。只需通过处理启动包中的
	 * 任何选项来完成后端启动，我们就完成了。
	 */
	if (am_walsender && !am_db_walsender)
	{
		/* 处理启动包中传递的任何选项 */
		if (MyProcPort != NULL)
			fc_process_startup_options(MyProcPort, fc_am_superuser);

		/* 在我们读取所有选项后立即应用PostAuthDelay */
		if (PostAuthDelay > 0)
			pg_usleep(PostAuthDelay * 1000000L);

		/* 初始化客户端编码 */
		InitializeClientEncoding();

		/* 在 PgBackendStatus 数组中报告此后端 */
		pgstat_bestart();

		/* 关闭我们上面启动的事务 */
		CommitTransactionCommand();

		return;
	}

	/*
	 * 设置持有数据库ID和默认表空间的全局变量。
	 * 但请注意，我们实际上还不会尝试接触数据库。
	 *
	 * 在引导情况下我们采取了一条快捷方式，否则我们必须查找
	 * 在pg_database中的数据库条目。
	 */
	if (fc_bootstrap)
	{
		fc_dboid = Template1DbOid;
		MyDatabaseTableSpace = DEFAULTTABLESPACE_OID;
	}
	else if (fc_in_dbname != NULL)
	{
		HeapTuple	fc_tuple;
		Form_pg_database fc_dbform;

		fc_tuple = fc_GetDatabaseTuple(fc_in_dbname);
		if (!HeapTupleIsValid(fc_tuple))
			ereport(FATAL,
					(errcode(ERRCODE_UNDEFINED_DATABASE),
					 errmsg("database \"%s\" does not exist", fc_in_dbname)));
		fc_dbform = (Form_pg_database) GETSTRUCT(fc_tuple);
		fc_dboid = fc_dbform->oid;
	}
	else if (!OidIsValid(fc_dboid))
	{
		/*
		 * 如果这是一个不绑定到任何特定
		 * 数据库的后台工作进程，我们现在就完成了。后续的所有
		 * 操作只有在我们绑定到特定数据库时才有意义。我们需要
		 * 在返回之前关闭我们启动的事务。
		 */
		if (!fc_bootstrap)
		{
			pgstat_bestart();
			CommitTransactionCommand();
		}
		return;
	}

	/*
	 * 现在，对我们试图连接的数据库进行写锁定。
	 * 如果该数据库上有一个并发运行的DROP DATABASE，这将
	 * 阻止我们直到它完成（并提交其对pg_database的更新）。
	 *
	 * 请注意，锁定不会持有太久，仅持续到这个启动
	 * 事务结束。这是可以的，因为我们将在放弃锁定之前
	 * 在ProcArray中宣传我们对数据库的使用（事实上，这就是
	 * 下一步要做的）。在此之后尝试进行DROP DATABASE的
	 * 任何人，一旦获得锁，将会在数组中看到我们。顺序非常
	 * 重要，因为我们不想在获得锁之前宣称自己在这个
	 * 数据库中；否则，我们将造成与CountOtherDBBackends()
	 * 的死锁。
	 *
	 * 注意：在这里使用RowExclusiveLock是合理的，因为我们设想
	 * 我们的会话是数据库的并发写入者。如果我们能声明一个会话
	 * 是保证只读的，我们可以为这样的会话使用AccessShareLock，
	 * 从而不与CREATE DATABASE发生冲突。
	 */
	if (!fc_bootstrap)
		LockSharedObject(DatabaseRelationId, fc_dboid, 0, RowExclusiveLock);

	/*
	 * 重新检查pg_database以确保目标数据库没有消失。
	 * 如果存在并发的DROP DATABASE，这确保我们将干净地退出，
	 * 而不会造成混乱。
	 */
	if (!fc_bootstrap)
	{
		HeapTuple	fc_tuple;
		Form_pg_database fc_datform;

		fc_tuple = fc_GetDatabaseTupleByOid(fc_dboid);
		if (HeapTupleIsValid(fc_tuple))
			fc_datform = (Form_pg_database) GETSTRUCT(fc_tuple);

		if (!HeapTupleIsValid(fc_tuple) ||
			(fc_in_dbname && namestrcmp(&fc_datform->datname, fc_in_dbname)))
		{
			if (fc_in_dbname)
				ereport(FATAL,
						(errcode(ERRCODE_UNDEFINED_DATABASE),
						 errmsg("database \"%s\" does not exist", fc_in_dbname),
						 errdetail("It seems to have just been dropped or renamed.")));
			else
				ereport(FATAL,
						(errcode(ERRCODE_UNDEFINED_DATABASE),
						 errmsg("database %u does not exist", fc_dboid)));
		}

		strlcpy(fc_dbname, NameStr(fc_datform->datname), sizeof(fc_dbname));

		if (database_is_invalid_form(fc_datform))
		{
			ereport(FATAL,
					errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					errmsg("cannot connect to invalid database \"%s\"", fc_dbname),
					errhint("Use DROP DATABASE to drop invalid databases."));
		}

		MyDatabaseTableSpace = fc_datform->dattablespace;
		/* 将数据库名称传回调用者 */
		if (fc_out_dbname)
			strcpy(fc_out_dbname, fc_dbname);
	}

	/*
	 * 现在我们重新检查后，我们可以肯定已经连接到一个
	 * 数据库，因此可以设置MyDatabaseId。
	 *
	 * 重要的是，MyDatabaseId的设置必须在我们确定
	 * 目标数据库不能被并发删除或重命名后进行。例如，
	 * 如果没有这种保证，pgstat_update_dbstats()可能会在pgstat
	 * 关闭回调中为刚删除的数据库创建条目，这可能会混淆其他
	 * 代码路径，比如自动清理调度程序。
	 */
	MyDatabaseId = fc_dboid;

	/*
	 * 现在我们可以用数据库 ID 标记我们的 PGPROC 条目。
	 *
	 * 我们假设这是一个原子存储，因此不需要锁；尽管实际上
	 * 即使不是原子的，事情也会正常工作。任何在 ProcArray 中搜索
	 * 该数据库 ID 的人应该持有数据库锁，因此他们不会与这个存储
	 * 并发执行。理论上，寻找另一个数据库 ID 的进程如果读取了一个
	 * 部分更新的 databaseId 值，可能会看到一个匹配；但只要所有这样的
	 * 搜索都等待并重试，如 CountOtherDBBackends() 中所示，它们肯定会在
	 * 下次尝试时看到正确的值。
	 */
	MyProc->databaseId = MyDatabaseId;

	/*
	 * 在读取 pg_authid 和/或 pg_database 时，我们建立了一个目录快照；但
	 * 在我们设置 MyDatabaseId 之前，我们不会对未共享目录的传入 sinval
	 * 消息做出反应，因此我们不会意识到快照已失效。假设它不再有效。
	 */
	InvalidateCatalogSnapshot();

	/*
	 * 现在我们应该能够安全访问数据库目录。验证它是否存在且看起来合理。
	 */
	fc_fullpath = GetDatabasePath(MyDatabaseId, MyDatabaseTableSpace);

	if (!fc_bootstrap)
	{
		if (access(fc_fullpath, F_OK) == -1)
		{
			if (errno == ENOENT)
				ereport(FATAL,
						(errcode(ERRCODE_UNDEFINED_DATABASE),
						 errmsg("database \"%s\" does not exist",
								fc_dbname),
						 errdetail("The database subdirectory \"%s\" is missing.",
								   fc_fullpath)));
			else
				ereport(FATAL,
						(errcode_for_file_access(),
						 errmsg("could not access directory \"%s\": %m",
								fc_fullpath)));
		}

		ValidatePgVersion(fc_fullpath);
	}

	SetDatabasePath(fc_fullpath);
	pfree(fc_fullpath);

	/*
	 * 现在可以真实地访问系统目录了。
	 *
	 * 为系统目录加载 relcache 条目。这必须创建至少最小的“固定”
	 * 缓存条目集。
	 */
	RelationCacheInitializePhase3();

	/* 设置 ACL 框架（以便 CheckMyDatabase 可以检查权限） */
	initialize_acl();

	/*
	 * 重新读取我们数据库的 pg_database 行，检查权限并设置
	 * 数据库特定的 GUC 设置。在所有数据库访问基础设施搭建完成之前，
	 * 我们无法做到这一点。（此外，它想知道用户是否为超级用户，
	 * 所以上述内容必须先发生。）
	 */
	if (!fc_bootstrap)
		fc_CheckMyDatabase(fc_dbname, fc_am_superuser, fc_override_allow_connections);

	/*
	 * 现在处理任何命令行开关和在启动数据包中传入的任何附加 GUC 变量
	 * 设置。我们之前不能这样做，因为我们不知道客户是否为超级用户。
	 */
	if (MyProcPort != NULL)
		fc_process_startup_options(MyProcPort, fc_am_superuser);

	/* 处理 pg_db_role_setting 选项 */
	fc_process_settings(MyDatabaseId, GetSessionUserId());

	/* 在我们读取所有选项后立即应用PostAuthDelay */
	if (PostAuthDelay > 0)
		pg_usleep(PostAuthDelay * 1000000L);

	/*
	 * 初始化各种默认状态，在选择活动用户并获得正确的 GUC 设置之前
	 * 无法设置。
	 */

	/* 设置默认命名空间搜索路径 */
	InitializeSearchPath();

	/* 初始化客户端编码 */
	InitializeClientEncoding();

	/* 初始化此后端的会话状态。 */
	InitializeSession();

	/*
	 * 如果这是一个交互会话，加载在后端启动时应该预加载的任何库。
	 * 因为这些是由 GUC 决定的，所以在 GUC 设置完成之前无法发生，
	 * 但我们希望在初始事务期间发生，以防任何需要数据库访问的工作。
	 */
	if (fc_load_session_libraries)
		process_session_preload_libraries();

	/* 在 PgBackendStatus 数组中报告此后端 */
	if (!fc_bootstrap)
		pgstat_bestart();

	/* 关闭我们上面启动的事务 */
	if (!fc_bootstrap)
		CommitTransactionCommand();
}

/*
 * 处理任何命令行开关和在启动数据包中传入的任何附加 GUC 变量
 * 设置。
 */
static void fc_process_startup_options(Port *fc_port, bool fc_am_superuser)
{
	GucContext	fc_gucctx;
	ListCell   *fc_gucopts;

	fc_gucctx = fc_am_superuser ? PGC_SU_BACKEND : PGC_BACKEND;

	/*
	 * 首先处理启动数据包中包含的任何命令行开关，如果我们在常规后端中。
	 */
	if (fc_port->cmdline_options != NULL)
	{
		/*
		 * 从 port->cmdline_options 中得到的命令行参数的最大可能数量是 (strlen + 1) / 2；见 pg_split_opts()。
		 */
		char	  **fc_av;
		int			fc_maxac;
		int			fc_ac;

		fc_maxac = 2 + (strlen(fc_port->cmdline_options) + 1) / 2;

		fc_av = (char **) palloc(fc_maxac * sizeof(char *));
		fc_ac = 0;

		fc_av[fc_ac++] = "postgres";

		pg_split_opts(fc_av, &fc_ac, fc_port->cmdline_options);

		fc_av[fc_ac] = NULL;

		Assert(fc_ac < fc_maxac);

		(void) process_postgres_switches(fc_ac, fc_av, fc_gucctx, NULL);
	}

	/*
	 * 处理启动数据包中传递的任何额外GUC变量设置。
	 * 它们的处理方式与命令行变量完全相同。
	 */
	fc_gucopts = list_head(fc_port->guc_options);
	while (fc_gucopts)
	{
		char	   *fc_name;
		char	   *fc_value;

		fc_name = lfirst(fc_gucopts);
		fc_gucopts = lnext(fc_port->guc_options, fc_gucopts);

		fc_value = lfirst(fc_gucopts);
		fc_gucopts = lnext(fc_port->guc_options, fc_gucopts);

		SetConfigOption(fc_name, fc_value, fc_gucctx, PGC_S_CLIENT);
	}
}

/*
 * 从 pg_db_role_setting 加载 GUC 设置。
 *
 * 我们尝试针对数据库/角色组合的特定设置，以及针对此数据库和此用户的一般设置。
 */
static void fc_process_settings(Oid fc_databaseid, Oid fc_roleid)
{
	Relation	fc_relsetting;
	Snapshot	fc_snapshot;

	if (!IsUnderPostmaster)
		return;

	fc_relsetting = table_open(DbRoleSettingRelationId, AccessShareLock);

	/* 为了提高效率，读取同一快照下的所有设置 */
	fc_snapshot = RegisterSnapshot(GetCatalogSnapshot(DbRoleSettingRelationId));

	/* 如果设置更早，后面的设置会被忽略。 */
	ApplySetting(fc_snapshot, fc_databaseid, fc_roleid, fc_relsetting, PGC_S_DATABASE_USER);
	ApplySetting(fc_snapshot, InvalidOid, fc_roleid, fc_relsetting, PGC_S_USER);
	ApplySetting(fc_snapshot, fc_databaseid, InvalidOid, fc_relsetting, PGC_S_DATABASE);
	ApplySetting(fc_snapshot, InvalidOid, InvalidOid, fc_relsetting, PGC_S_GLOBAL);

	UnregisterSnapshot(fc_snapshot);
	table_close(fc_relsetting, AccessShareLock);
}

/*
 * 后台关机回调。进行我们希望在所有支持模块通过自己的回调关闭之前执行的清理工作。
 *
 * 用户级的清理，例如临时关系的移除和 UNLISTEN，通过在此之前执行的单独回调进行。我们不将回调结合起来，因为如果用户级清理失败，我们仍然希望这个回调能够执行。
 */
static void fc_ShutdownPostgres(int fc_code, Datum fc_arg)
{
	/* 确保我们已经终止了任何活动事务 */
	AbortOutOfAnyTransaction();

	/*
	 * 用户锁不会在事务结束时释放，因此务必明确释放它们。
	 */
	LockReleaseAll(USER_LOCKMETHOD, true);
}


/*
 * STATEMENT_TIMEOUT 处理程序：触发查询取消中断。
 */
static void fc_StatementTimeoutHandler(void)
{
	int			fc_sig = SIGINT;

	/*
	 * 在身份验证期间，超时用于处理 authentication_timeout - 我们希望响应这种超时而退出。
	 */
	if (ClientAuthInProgress)
		fc_sig = SIGTERM;

#ifdef HAVE_SETSID
	/* 尝试向整个进程组发送信号 */
	kill(-MyProcPid, fc_sig);
#endif
	kill(MyProcPid, fc_sig);
}

/*
 * LOCK_TIMEOUT 处理程序：触发查询取消中断。
 */
static void fc_LockTimeoutHandler(void)
{
#ifdef HAVE_SETSID
	/* 尝试向整个进程组发送信号 */
	kill(-MyProcPid, SIGINT);
#endif
	kill(MyProcPid, SIGINT);
}

static void fc_IdleInTransactionSessionTimeoutHandler(void)
{
	IdleInTransactionSessionTimeoutPending = true;
	InterruptPending = true;
	SetLatch(MyLatch);
}

static void fc_IdleSessionTimeoutHandler(void)
{
	IdleSessionTimeoutPending = true;
	InterruptPending = true;
	SetLatch(MyLatch);
}

static void fc_IdleStatsUpdateTimeoutHandler(void)
{
	IdleStatsUpdateTimeoutPending = true;
	InterruptPending = true;
	SetLatch(MyLatch);
}

static void fc_ClientCheckTimeoutHandler(void)
{
	CheckClientConnectionPending = true;
	InterruptPending = true;
	SetLatch(MyLatch);
}

/*
 * 如果此数据库集群中定义了至少一个角色，则返回 true。
 */
static bool fc_ThereIsAtLeastOneRole(void)
{
	Relation	fc_pg_authid_rel;
	TableScanDesc fc_scan;
	bool		fc_result;

	fc_pg_authid_rel = table_open(AuthIdRelationId, AccessShareLock);

	fc_scan = table_beginscan_catalog(fc_pg_authid_rel, 0, NULL);
	fc_result = (heap_getnext(fc_scan, ForwardScanDirection) != NULL);

	table_endscan(fc_scan);
	table_close(fc_pg_authid_rel, AccessShareLock);

	return fc_result;
}
